from typing import Callable, List


class StringMetrics:
    @staticmethod
    def q_grams_distance(q: int=3, padding_char: str='#', tokenizer=None) -> Callable[[str, str], float]:
        def tokenize(s: str) -> List[str]:
            if tokenizer:
                return tokenizer(s)
            return s.split()
        def similarity(s1: str, s2: str) -> float:
            def q_gram_with_padding(s: List[str], q: int, padding_char: str):
                padding = [padding_char] * (q - 1)
                s_padded = padding + s + padding
                q_grams = [tuple(s_padded[i:i+q]) for i in range(len(s_padded) - q + 1)]
                return set(q_grams)
            
            tokens1 = tokenize(s1)
            tokens2 = tokenize(s2)
            
            q_grams1 = q_gram_with_padding(tokens1, q, padding_char)
            q_grams2 = q_gram_with_padding(tokens2, q, padding_char)
            
            intersection = q_grams1 & q_grams2
            union = q_grams1 | q_grams2
            
            if len(union) == 0:
                return 0.0
            return 1.0 - (len(intersection) / len(union))
        
        return similarity